package com.taglab.chic.atom.syndication;

import com.taglab.chic.atom.AtomEntry;
import com.taglab.chic.atom.AtomFeed;
import com.taglab.chic.atom.AtomSource;
import com.taglab.chic.atom.BaseAtomElement;
import com.taglab.chic.atom.Category;
import com.taglab.chic.atom.Content;
import com.taglab.chic.atom.Link;
import com.taglab.chic.atom.Person;
import com.taglab.chic.atom.Text;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.time.DateFormatUtils;
import org.codehaus.stax2.XMLStreamWriter2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.OutputStream;
import java.util.Date;

/**
 * @author Markus Kobler
 */
public class AtomFeedWriter {

  private final Logger logger = LoggerFactory.getLogger(AtomFeedWriter.class);

  public static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom";
  public static final String XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml";

  private static XMLOutputFactory outputFactory;
  protected final XMLStreamWriter writer;
  
  private String defaultNamespace = ATOM_NAMESPACE;
  private String atomPrefix = "atom";

  public AtomFeedWriter(XMLStreamWriter writer) {
    this( writer, "UTF-8");
  }

  public AtomFeedWriter(XMLStreamWriter writer, String codec) {
    this.writer = writer;
  }

  public AtomFeedWriter(OutputStream output) throws XMLStreamException {
    this(output, "UTF-8");
  }
  
  public AtomFeedWriter(OutputStream output, String encoding) throws XMLStreamException {
    if( outputFactory == null )outputFactory = XMLOutputFactory.newInstance();
    this.writer = outputFactory.createXMLStreamWriter(output, encoding);
  }

  public void write(AtomFeed feed) throws XMLStreamException {
    write(feed, feed.getEntries() );
  }  

  public void write(AtomFeed feed, Iterable<AtomEntry> entries) throws XMLStreamException {

    doStart();

    writeBaseElement(feed);
    writeStringTag(feed.getIcon(), "icon");
    writeStringTag(feed.getLogo(), "logo");

    if( entries != null ) {
      writeAll(feed.getEntries(), "entry");
    } else {
      logger.warn("Invalid feed, no entries");
    }

    doEnd();
  }

  public void doStart() throws XMLStreamException {
    writer.setDefaultNamespace(defaultNamespace);
    writer.writeStartDocument();
    writer.writeStartElement(ATOM_NAMESPACE, "feed");
    writer.writeDefaultNamespace(defaultNamespace);
    if( !defaultNamespace.equals(ATOM_NAMESPACE) ) {
      writer.writeNamespace(atomPrefix, ATOM_NAMESPACE);
    }
  }

  public void doEnd() throws XMLStreamException{
    writer.writeEndElement();
    writer.writeEndDocument();
  }

  protected void writeStringTag(String value, String tagName)  throws XMLStreamException {
    if( value == null ) return;
    writer.writeStartElement(defaultNamespace, tagName);
    writer.writeCharacters(value);
    writer.writeEndElement();
  }

  protected void writeDateTag(Date date, String tagName) throws XMLStreamException {
    if( date == null ) return;
    writer.writeStartElement(defaultNamespace, tagName);
    writer.writeCharacters(DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.format(date));
    writer.writeEndElement();
  }

  protected void writeLink(Link link) throws XMLStreamException {
    if( link == null ) return;
    writer.writeStartElement(defaultNamespace, "link");
    writeAttribute(link.getRel(),      "rel");
    writeAttribute(link.getType(),     "type");
    writeAttribute(link.getTitle(),    "title");
    writeAttribute(link.getHref(),     "href");
    writeAttribute(link.getHreflang(), "hreflang");
    if( link.getLength() > 0 ) {
      writeAttribute(Long.toString(link.getLength()), "length");
    }
    writer.writeEndElement();
  }

  protected void writePerson(Person person, String tagName) throws XMLStreamException {
    if( person == null ) return;
    writer.writeStartElement(defaultNamespace, tagName);
    writeStringTag(person.getName(),  "name");
    writeStringTag(person.getUri(),   "uri");
    writeStringTag(person.getEmail(), "email");
    writer.writeEndElement();
  }

  protected void writeCategory(Category category) throws XMLStreamException {
    if( category == null ) return;
    writer.writeStartElement(defaultNamespace, "category");
    writeAttribute(category.getTerm(),   "term");
    writeAttribute(category.getScheme(), "scheme");
    writeAttribute(category.getLabel(),  "label");
    writer.writeEndElement();
  }

  // XXX too much duplication with text...
  protected void writeContent(Content content) throws XMLStreamException {
    if( content == null ) return;
    writer.writeStartElement(defaultNamespace, "content");
    String type = content.getType();
    writeAttribute( type, "type" );
    String src = content.getSrc();
    if( src != null ) {
      writeAttribute(src, "src");
      // XXX check if type set?
    } else {
       // XXX Enable/Optimize for InputStream's?
      if( type == null ) type = "text";
      if( "xhtml".equals(type) ) {
        writer.writeStartElement("div");
        writer.writeNamespace("xmlns", XHTML_NAMESPACE);
        ((XMLStreamWriter2)writer).writeRaw(content.getContent().toString());
        writer.writeEndElement();
      } else if( type.endsWith("/xml") || type.endsWith("+xml")) {
        ((XMLStreamWriter2)writer).writeRaw(content.getContent().toString());
      } else if( "html".equals(type) ||  type.startsWith("text") ) {
        writer.writeCharacters(content.getContent().toString());
      } else {
        // XXX optimize
        ((XMLStreamWriter2)writer).writeRaw(new String(Base64.encodeBase64(content.getContent().toString().getBytes())));
      }
    }
    writer.writeEndElement();
  }  

  protected void writeText(Text text, String tagName) throws XMLStreamException {
    if( text == null ) return;
    writer.writeStartElement(defaultNamespace, tagName);
    String type = text.getType();
    writeAttribute( type, "type" );    
    // XXX Enable/Optimize for InputStream's?
    if( type == null ) type = "text";
    if( "xhtml".equals(type) ) {
      writer.writeStartElement("div");
      writer.writeNamespace("xmlns", XHTML_NAMESPACE);
      ((XMLStreamWriter2)writer).writeRaw(text.getContent().toString());
      writer.writeEndElement();
    } else if( type.endsWith("/xml") || type.endsWith("+xml")) {
      ((XMLStreamWriter2)writer).writeRaw(text.getContent().toString());
    } else if( "html".equals(type) ||  type.startsWith("text") ) {
      writer.writeCharacters(text.getContent().toString());
    } else {
      // XXX optimize
      ((XMLStreamWriter2)writer).writeRaw(new String(Base64.encodeBase64(text.getContent().toString().getBytes())));
    }
    writer.writeEndElement();
  }

  protected void writeEntry(AtomEntry entry) throws XMLStreamException {
    writer.writeStartElement(ATOM_NAMESPACE, "entry");

    writeBaseElement(entry);
    
    writeDateTag(entry.getPublished(), "published");
    writeContent(entry.getContent());    
    writeText(entry.getSummary(), "summary");
    
    AtomSource source = entry.getSource();    
    if( source != null ) {
      writeBaseElement(source);
      writeStringTag(source.getIcon(), "icon");
      writeStringTag(source.getLogo(), "logo");      
    }

    writer.writeEndElement();
  }

  protected <T> void writeAll(Iterable<T> items, String defaultTagName) throws XMLStreamException {
    if( items == null ) return;
    for( T item : items ) writeObjectTag(item, defaultTagName);
  }

  protected void writeObjectTag(Object value, String defaultTagName)  throws XMLStreamException {
    
    if( value == null ) return;

    if( value instanceof String ) {

      writeStringTag((String)value, defaultTagName);

    } else if( value instanceof AtomEntry ) {

      writeEntry((AtomEntry)value);

    } else if( value instanceof Link ) {

      writeLink((Link)value);

    } else if( value instanceof Person ) {

      writePerson((Person)value, defaultTagName);

    } else if( value instanceof Category ) {

      writeCategory((Category)value);

    } else if( value instanceof Iterable ) {

      writeAll((Iterable<Object>)value, defaultTagName);

    } else {

      writeStringTag(value.toString(), defaultTagName);

    }
  }

  protected void writeBaseElement(BaseAtomElement element) throws XMLStreamException {
    writeStringTag(element.getId(),    "id");
    writeStringTag(element.getTitle(), "title");
    writeDateTag(element.getUpdated(), "updated"); // XXX calculate update?
    writeAll(element.getAuthors(),     "author");
    writeAll(element.getCategories(),  "category");
    writeAll(element.getLinks(),       "link");
    writeText(element.getRights(),     "rights");
  }

  protected void writeAttribute(String value, String attName) throws XMLStreamException {
    if( value == null ) return;
//      writer.writeAttribute(defaultNamespace, attName, value.toString());
    writer.writeAttribute(attName, value);
  }

}
